package com.code.soulnetty.modbus.biz;

import com.code.soulnetty.modbus.client.service.DataType;

import java.math.BigInteger;

import static com.code.soulnetty.modbus.biz.Utils.HEX_NUM;

/**
 * 处理解析
 *
 * @author jdy
 * @date 5:19 下午
 **/
public class Bits {


    private Bits() {
    }

    public static Number toNumber(byte[] data, int offset, DataType dataType, boolean littleEndian,
                                  boolean registerSwap, double zoom, double baseValue, byte bitCount) {
        Number tmp = toNumber(data, offset, dataType, littleEndian, registerSwap, bitCount);
        if (tmp != null) {
            if (zoom != 0 && zoom != 1) {
                tmp = tmp.doubleValue() * zoom;
            }
            if (baseValue != 0) {
                tmp = tmp.doubleValue() + baseValue;
            }
            return tmp;
        }
        return null;
    }

    private static Number toNumber(byte[] data, int offset, DataType dataType, boolean littleEndian,
                                   boolean registerSwap, byte bitCount) {
        switch (dataType) {
            case BOOLEAN:
                return toBoolean(data, offset, littleEndian);
            case BIT_INT:
                return toBitInt(data, offset, bitCount, littleEndian);
            case SHORT:
                return toShort(data, offset, littleEndian);
            case SHORT_UNSIGNED:
                return toShortUnsigned(data, offset, littleEndian);
            case INT:
                return toInt(data, offset, littleEndian, registerSwap);
            case TWO_REG_COM:
            case INT_UNSIGNED:
                return toIntUnsigned(data, offset, littleEndian, registerSwap);
            case LONG:
                return toLong(data, offset, littleEndian, registerSwap);
            case FLOAT:
                return toFloat(data, offset, littleEndian, registerSwap);
            case DOUBLE:
                return toDouble(data, offset, littleEndian, registerSwap);
            case BCD:
                return toBCD(data, offset, littleEndian);
            case BCD_FOUR:
                return toBCDFour(data, offset, littleEndian, registerSwap);
            case LONG_UNSIGNED:
                return toLongUnsigned(data, offset, littleEndian, registerSwap);
            case TIME:
                return toTime(data, offset, littleEndian, registerSwap);
            case DTIME:
                return toDtime(data, offset, littleEndian, registerSwap);
            case THREE_REG_COM:
                return toThreeRegCom(data, offset, littleEndian, registerSwap);
            default:
                return null;
        }
    }

    private static Number toDtime(byte[] data, int offset, boolean littleEndian, boolean registerSwap) {
        return null;
    }

    /**
     * @param data         字节流
     * @param offset       偏移
     * @param littleEndian 是否小端
     * @param registerSwap 是否支持寄存器点位转换
     * @return 日期时间 202201010101001
     */
    private static Number toTime(byte[] data, int offset, boolean littleEndian, boolean registerSwap) {
        if (littleEndian) {
            if (registerSwap) {
                return makeTime(data[offset + 6], data[offset + 7], data[offset + 4], data[offset + 5],
                        data[offset + 2], data[offset + 3], data[offset], data[offset + 1]);
            }
            return makeTime(data[offset + 7], data[offset + 6], data[offset + 5], data[offset + 4],
                    data[offset + 3], data[offset + 2], data[offset + 1], data[offset]);
        }
        if (registerSwap) {
            return makeTime(data[offset + 1], data[offset], data[offset + 3], data[offset + 2],
                    data[offset + 5], data[offset + 4], data[offset + 7], data[offset + 6]);
        }
        return makeLong(data[offset], data[offset + 1], data[offset + 2], data[offset + 3], data[offset + 4],
                data[offset + 5], data[offset + 6], data[offset + 7]);
    }

    /**
     * @param b0 第一个字节 年
     * @param b1 第二个字节 月
     * @param b2 第三个字节 日
     * @param b3 第四个字节 时
     * @param b4 第五个字节 分
     * @param b5 第六个字节 秒
     * @param b6 第七个字节 和第八个字节作为 毫秒
     * @param b7 第八个字节
     * @return 表示时间的数值 202201010101001
     */
    static long makeTime(byte b0, byte b1, byte b2, byte b3, byte b4, byte b5, byte b6, byte b7) {
        StringBuilder sb = new StringBuilder("20");
        sb.append(b0)
                .append(b1 < 10 ? "0" : "").append(b1)
                .append(b2 < 10 ? "0" : "").append(b2)
                .append(b3 < 10 ? "0" : "").append(b3)
                .append(b4 < 10 ? "0" : "").append(b4)
                .append(b5 < 10 ? "0" : "").append(b5);
        short ms = makeShort(b6, b7);
        sb.append(ms < 10 ? "00" : (ms < 100 ? "0" : "")).append(ms);
        return Long.parseLong(sb.toString());
    }

    private static Number toLongUnsigned(byte[] data, int offset, boolean littleEndian, boolean registerSwap) {
        if (littleEndian) {
            if (registerSwap) {
                return makeBigInter(new byte[]{0, data[offset + 6], data[offset + 7], data[offset + 4],
                        data[offset + 5], data[offset + 2], data[offset + 3], data[offset], data[offset + 1]});
            }
            return makeBigInter(new byte[]{0, data[offset + 7], data[offset + 6], data[offset + 5],
                    data[offset + 4], data[offset + 3], data[offset + 2], data[offset + 1], data[offset]});
        }
        if (registerSwap) {
            return makeBigInter(new byte[]{0, data[offset + 1], data[offset], data[offset + 3],
                    data[offset + 2], data[offset + 5], data[offset + 4], data[offset + 7], data[offset + 6]});
        }
        byte[] tmp = new byte[9];
        System.arraycopy(data, offset, tmp, 1, 8);
        return makeBigInter(tmp);
    }

    private static Number toThreeRegCom(byte[] data, int offset, boolean littleEndian, boolean registerSwap) {
        if (littleEndian) {
            if (registerSwap) {
               return makeLong((byte) 0, (byte) 0, data[offset + 2], data[offset + 3], data[offset], data[offset + 1], data[offset + 4], data[offset + 5]);
            }
            return makeLong((byte) 0, (byte) 0, data[offset + 3], data[offset + 2], data[offset + 1], data[offset], data[offset + 5], data[offset + 4]);
        }
        if (registerSwap) {
            return makeLong((byte) 0, (byte) 0, data[offset + 1], data[offset], data[offset + 3], data[offset + 2], data[offset + 5], data[offset + 4]);
        }
        return makeLong((byte) 0, (byte) 0, data[offset], data[offset + 1], data[offset + 2], data[offset + 3], data[offset + 4], data[offset + 5]);
    }

    private static Number toBCDFour(byte[] data, int offset, boolean littleEndian, boolean registerSwap) {
        if (littleEndian) {
            if (registerSwap) {
                return makeBCD(data[offset + 2], data[offset + 3], data[offset], data[offset + 1]);
            }
            return makeBCD(data[offset + 3], data[offset + 2], data[offset + 1], data[offset]);
        }
        if (registerSwap) {
            return makeBCD(data[offset + 1], data[offset], data[offset + 3], data[offset + 2]);
        }
        return makeBCD(data[offset], data[offset + 1], data[offset + 2], data[offset + 3]);
    }

    private static Number toBCD(byte[] data, int offset, boolean littleEndian) {
        if (littleEndian) {
            return makeBCD(data[offset + 1], data[offset]);
        }
        return makeBCD(data[offset], data[offset + 1]);
    }

    private static Number toDouble(byte[] data, int offset, boolean littleEndian, boolean registerSwap) {
        if (littleEndian) {
            if (registerSwap) {
                return Double.longBitsToDouble(makeLong(data[offset + 6], data[offset + 7], data[offset + 4],
                        data[offset + 5], data[offset + 2], data[offset + 3], data[offset], data[offset + 1]));
            }
            return Double.longBitsToDouble(makeLong(data[offset + 7], data[offset + 6], data[offset + 5],
                    data[offset + 4], data[offset + 3], data[offset + 2], data[offset + 1], data[offset]));
        }
        if (registerSwap) {
            return Double.longBitsToDouble(makeLong(data[offset + 1], data[offset], data[offset + 3],
                    data[offset + 2], data[offset + 5], data[offset + 4], data[offset + 7], data[offset + 6]));
        }
        return Double.longBitsToDouble(makeLong(data[offset], data[offset + 1], data[offset + 2],
                data[offset + 3], data[offset + 4], data[offset + 5], data[offset + 6], data[offset + 7]));
    }

    private static Number toFloat(byte[] data, int offset, boolean littleEndian, boolean registerSwap) {
        if (littleEndian) {
            if (registerSwap) {
                return Float.intBitsToFloat(makeInt(data[offset + 2], data[offset + 3], data[offset],
                        data[offset + 1]));
            }
            return Float.intBitsToFloat(makeInt(data[offset + 3], data[offset + 2], data[offset + 1],
                    data[offset]));
        }
        if (registerSwap) {
            return Float.intBitsToFloat(makeInt(data[offset + 1], data[offset], data[offset + 3],
                    data[offset + 2]));
        }
        return Float.intBitsToFloat(makeInt(data[offset], data[offset + 1], data[offset + 2],
                data[offset + 3]));
    }

    private static Number toLong(byte[] data, int offset, boolean littleEndian, boolean registerSwap) {
        if (littleEndian) {
            if (registerSwap) {
                return makeLong(data[offset + 6], data[offset + 7], data[offset + 4], data[offset + 5],
                        data[offset + 2], data[offset + 3], data[offset], data[offset + 1]);
            }
            return makeLong(data[offset + 7], data[offset + 6], data[offset + 5], data[offset + 4],
                    data[offset + 3], data[offset + 2], data[offset + 1], data[offset]);
        }
        if (registerSwap) {
            return makeLong(data[offset + 1], data[offset], data[offset + 3], data[offset + 2],
                    data[offset + 5], data[offset + 4], data[offset + 7], data[offset + 6]);
        }
        return makeLong(data[offset], data[offset + 1], data[offset + 2], data[offset + 3], data[offset + 4],
                data[offset + 5], data[offset + 6], data[offset + 7]);
    }

    private static Number toIntUnsigned(byte[] data, int offset, boolean littleEndian, boolean registerSwap) {
        if (littleEndian) {
            if (registerSwap) {
                return makeLong(data[offset + 2], data[offset + 3], data[offset], data[offset + 1]);
            }
            return makeLong(data[offset + 3], data[offset + 2], data[offset + 1], data[offset]);
        }
        if (registerSwap) {
            return makeLong(data[offset + 1], data[offset], data[offset + 3], data[offset + 2]);
        }
        return makeLong(data[offset], data[offset + 1], data[offset + 2], data[offset + 3]);
    }

    private static Number toInt(byte[] data, int offset, boolean littleEndian, boolean registerSwap) {
        if (littleEndian) {
            if (registerSwap) {
                return makeInt(data[offset + 2], data[offset + 3], data[offset], data[offset + 1]);
            }
            return makeInt(data[offset + 3], data[offset + 2], data[offset + 1], data[offset]);
        }
        if (registerSwap) {
            return makeInt(data[offset + 1], data[offset], data[offset + 3], data[offset + 2]);
        }
        return makeInt(data[offset], data[offset + 1], data[offset + 2], data[offset + 3]);
    }

    private static Number toShortUnsigned(byte[] data, int offset, boolean littleEndian) {
        if (littleEndian) {
            return makeInt(data[offset + 1], data[offset]);
        }
        return makeInt(data[offset], data[offset + 1]);
    }

    private static Number toShort(byte[] data, int offset, boolean littleEndian) {
        if (littleEndian) {
            return makeShort(data[offset + 1], data[offset]);
        }
        return makeShort(data[offset], data[offset + 1]);
    }

    /**
     * 解析状态量的方法，每个字节内，从右到左每个bit的值为对应的DI值
     * eg:T:TRUE F:FALSE
     * 请求16个状态量返回结果为0x0302 对应的结果依次为 <TTFFFFFFTFFFFFF>
     * ----------------------    ------------------------------------------
     * |     content        |    |                  result                |
     * |____________________|    |________________________________________|
     * |       0x0302       | ==>| 1 2 3 4 5 6 7 8  9 10 11 12 13 14 15 16|
     * | 00000011 00000001  |    | T T F F F F F F  T  F  F F  F  F  F  F |
     * ---------------------|    |----------------------------------------|
     *
     * @param data   数据
     * @param offset 偏移位
     * @return 解析结果
     */
    private static Number toBoolean(byte[] data, int offset, boolean littleEndian) {
        int i = offset >> 3;
        int len = data.length;
        //当返回的长度为奇数时，默认是不能使用大端在前，因为大端在前有个前提是必须为偶数做字节的交换
        if (littleEndian || (len % 2 == 1 && i == len - 1)) {
            return (((data[i] & 0xff) >> (offset % 8)) & 0x1) == 1 ? HEX_NUM[1] : HEX_NUM[0];
        }
        return (((data[i % 2 == 0 ? i + 1 : i - 1] & 0xff) >> (offset % 8)) & 0x1) == 1 ? HEX_NUM[1] : HEX_NUM[0];
    }

    /**
     * 连续bit位数据
     *
     * @param data     数据
     * @param offset   偏移位
     * @param bitCount 连续bit位数量
     * @return 解析结果
     */
    private static Number toBitInt(byte[] data, int offset, byte bitCount, boolean littleEndian) {
        int i = offset >> 3;
        if (littleEndian) {
            return ((data[i] & 0xff) >> (offset % 8)) & (0xff >> (8 - bitCount));
        }
        return ((data[i % 2 == 0 ? i + 1 : i - 1] & 0xff) >> (offset % 8)) & (0xff >> (8 - bitCount));
    }

    static BigInteger makeBigInter(byte[] data) {
        return new BigInteger(data);
    }

    static int makeBCD(byte b3, byte b2, byte b1, byte b0) {
        StringBuilder sb = new StringBuilder();
        appendBCD(sb, b3);
        appendBCD(sb, b2);
        appendBCD(sb, b1);
        appendBCD(sb, b0);
        return Integer.parseInt(sb.toString());
    }

    static short makeBCD(byte b1, byte b0) {
        StringBuilder sbcbd = new StringBuilder();
        appendBCD(sbcbd, b1);
        appendBCD(sbcbd, b0);
        return Short.parseShort(sbcbd.toString());
    }

    static void appendBCD(StringBuilder sb, byte b) {
        sb.append(bcdNibbleToInt(b, true));
        sb.append(bcdNibbleToInt(b, false));
    }

    static int bcdNibbleToInt(byte b, boolean high) {
        int n;
        if (high) {
            n = (b >> 4) & 0xf;
        } else {
            n = b & 0xf;
        }
        if (n > 9) {
            n = 0;
        }
        return n;
    }

    /**
     * 构造4字节int类型
     *
     * @param b3
     * @param b2
     * @param b1
     * @param b0
     * @return
     */
    public static int makeInt(byte b3, byte b2, byte b1, byte b0) {
        return (b3 & 0xff) << 24 | (b2 & 0xff) << 16 | (b1 & 0xff) << 8 | (b0 & 0xff);
    }

    /**
     * 构造8字节long类型
     *
     * @param b7
     * @param b6
     * @param b5
     * @param b4
     * @param b3
     * @param b2
     * @param b1
     * @param b0
     * @return
     */
    public static long makeLong(byte b7, byte b6, byte b5, byte b4, byte b3, byte b2, byte b1, byte b0) {
        return ((((long) b7) << 56) |
                (((long) b6 & 0xff) << 48) |
                (((long) b5 & 0xff) << 40) |
                (((long) b4 & 0xff) << 32) |
                (((long) b3 & 0xff) << 24) |
                (((long) b2 & 0xff) << 16) |
                (((long) b1 & 0xff) << 8) |
                ((long) b0 & 0xff));
    }

    /**
     * 构造4字节long类型数据
     *
     * @param b3
     * @param b2
     * @param b1
     * @param b0
     * @return
     */
    public static long makeLong(byte b3, byte b2, byte b1, byte b0) {
        return (((long) b3 & 0xff) << 24) |
                (((long) b2 & 0xff) << 16) |
                (((long) b1 & 0xff) << 8) |
                ((long) b0 & 0xff);
    }

    public static short makeShort(byte b1, byte b0) {
        return (short) ((b1 << 8) | (b0 & 0xff));
    }

    public static int makeInt(byte b1, byte b0) {
        return makeShort(b1, b0) & 0xFFFF;
    }
}
